#include "project2_include.h"


#define MAX_DOMU 10
#include <assert.h>
/***** single copy*/

int *shm_id_array;
unsigned long int **ptrs_array;
unsigned int array_size = 1024;


/****/
//domain domain_array[MAX_DOMU];
__disk_sring_t *ring_array[MAX_DOMU];
__disk_back_ring_t backring_array[MAX_DOMU];
int flag_array[MAX_DOMU];

pthread_mutex_t client_queue_lock;   //lock for the following concurrent get requests
pthread_cond_t client_queue_delete;    //condition varialble for clients start.
pthread_cond_t client_queue_process;    //condition varialble for clients start.
static int client_queue_delete_mod = 0;
static int client_queue_process_mod = 1;

unsigned int msg_id;

extern int disk_file;
extern int page_size;
extern int page_limit;
extern unsigned int * disk_buffer;

unsigned int min_sector = 0;
unsigned int max_sector = 500;

unsigned int background = 1;
unsigned int active = 1;

pthread_t masterthread;
void SIGhandler(int); /* signal handler */
void quit(int);

int daemon_init(void)   
{   
	pid_t pid;   
	if((pid = fork()) < 0)   
		return(-1);   
	else if(pid != 0)   
	exit(0); /* parent exit */   
	/* child continues */   
	setsid(); /* become session leader */   
	chdir("."); /* change working directory */   
	umask(0); /* clear file mode creation mask */   
	close(0); /* close stdin */   
	close(1); /* close stdout */   
	close(2); /* close stderr */   
	return(0);   
}

static void set_timer()
{
	struct itimerval val;
	val.it_value.tv_sec=300;
	val.it_value.tv_usec=0;
	val.it_interval.tv_sec=300;
	val.it_interval.tv_usec=0;
	setitimer(ITIMER_REAL,&val,NULL);
	signal(SIGALRM,quit);
}

void quit(int sig)
{
	if(sig==SIGALRM)
	{
		if(active == 0)
		{
			printf("server quit due to idle\n");
			pthread_cancel(masterthread);
			exit(0);
		}
		else
		{
			set_timer();
		}
	}
	else
		printf("get timer failed!\n");
}
#define BATCH_SIZE 10
int lastDomU = 0;
static void *serve_loop() {
	disk_event_request_t request[BATCH_SIZE];
	disk_event_response_t response[BATCH_SIZE];
	int i, j, active_reqs, result;
	int idx[BATCH_SIZE];
	int ring_idx[BATCH_SIZE];
	unsigned long int * ptrs[BATCH_SIZE];
	//unsigned long int buf[BATCH_SIZE][SECTOR_SIZE/sizeof(unsigned long int)];
	//unsigned long int * ptrs1[1];
	active_reqs = 0;
	active = 0;
	i = (lastDomU + 1) % MAX_DOMU; // check the next domain U
	while (i < MAX_DOMU && active_reqs < BATCH_SIZE) {
		// trying to collect the BATCH_SIZE number of requests at once

		if (flag_array[i] == 1) {
			// find some requests
			active = 1;
			//printf("server ring #%d\n", i);//sleep(1);
			do {
				result = disk_event_get_request(&backring_array[i],
						&request[active_reqs]);
				if (result != -1) {

					for(j=0; j<array_size; j++)
					{
						if(request[active_reqs].shm_id == shm_id_array[j] || shm_id_array[j] == -1) /* shm_id already in array */
							break;
					}
					assert(j <= array_size);
					if (j < array_size && shm_id_array[j] == -1)
					{
						shm_id_array[j] = request[active_reqs].shm_id;
						ptrs_array[j] = shmat(request[active_reqs].shm_id, NULL, 0);
						//ptrs1[active_reqs] = ptrs_array[j];
					}
					else if (j == array_size)
					{
						array_size = array_size * 2;
						unsigned long int **temp = ptrs_array;
						ptrs_array = (unsigned long int *)malloc(sizeof(unsigned long int *)*array_size);
						memcpy(ptrs_array, temp, sizeof(unsigned long int *) * j);
						ptrs_array[j] = shmat(request[active_reqs].shm_id, NULL, 0);


						int *temp_id_array = shm_id_array;
						shm_id_array = (int *)malloc(sizeof(int *)*array_size);
						memcpy(shm_id_array, temp_id_array, sizeof(int *) * j);
						shm_id_array[j] = request[active_reqs].shm_id;
						

						
					}


					ptrs[active_reqs] = ptrs_array[j];

//					printf("got sector request %d from ring %d \n", request[active_reqs].sector_number, i);
					idx[active_reqs] = request[active_reqs].sector_number;
					//ptrs[active_reqs] = &response[active_reqs].sector[0];
					ring_idx[active_reqs] = i;
					active_reqs++;
				}
			} while (result != -1 && active_reqs < BATCH_SIZE);
		}

		i = i + 1;
		// go to the next domain u
		// once we reach the last domain, we stop
		// if we reached the last domain, the next serve_loop will start on the first domain
	}

	lastDomU = i - 1; // we have collected at least one request from this domain

	if (active_reqs > 0) {

//		printf("requesting sector_numbers: %d \n", active_reqs);
//		for (j = 0; j < active_reqs; j++) {
//			printf("%d -> %d ", j, request[j].sector_number);
//		}
//		printf("\n");

		if (cscan_read(&idx, ptrs, active_reqs) == -1) {
			printf("read disk error\n");
			exit(-1);
		};

		for (j = 0; j < active_reqs; j++) {
			response[j].domain_pid = request[j].domain_pid;
			response[j].domain_tid = request[j].domain_tid;
			response[j].sector_number = request[j].sector_number;
			response[j].shm_id = 0;
//			printf("first int of sector %d is %u \n", request[j].sector_number,
//					response[j].sector[0]);
//			printf("putting back to ring %d \n ", ring_idx[j]);
			disk_event_put_response(&backring_array[ring_idx[j]], &response[j]);
		}
	}
	set_timer();
}

static void *serve(void *url)
{
	while(1)
	{
		if (pthread_mutex_lock(&client_queue_lock)) /* acquire lock client_queue_lock */
			fprintf(stderr, "error in mutex lock\n");
		while(client_queue_delete_mod == 1){ /* wait until delete is finished */
			pthread_cond_wait(&client_queue_process, &client_queue_lock);
		}
		client_queue_process_mod = 1; /* enter process mode */
		serve_loop();
		client_queue_process_mod = 0; /* quit process mode */
		pthread_mutex_unlock(&client_queue_lock);
		if (pthread_cond_signal(&client_queue_delete)) /*signal client_queue_delete allowing delete */
			fprintf(stderr, "error in signaling condition variable\n");
	}
}

static void wait_new_client(int port)
{
	int i, sockfd, new_fd;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int sin_size;
	int nbytes;
	int portnumber = port;

	//create socket
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
		exit (1);
	}

	bzero(&server_addr, sizeof(struct sockaddr_in));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	server_addr.sin_port = htons(portnumber);

	//bind
	if(bind(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
	{
		fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
		exit (1);
	}

	//listen
	if(listen(sockfd, 10) == -1)//10 is backlog
	{
		fprintf(stderr, "Listen error:%s\n\a", strerror(errno));   
		exit (1);
	}
	while(1)   
	{
		//server block until client connects
		sin_size = sizeof(struct sockaddr_in);   
		if((new_fd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size)) == -1)
		{
			fprintf(stderr,   "Accept   error:%s\n\a",   strerror   (errno));
			exit (1);
		}
		//fprintf(stderr, "Connection from %s\n", inet_ntoa(client_addr.sin_addr));
		
		char buffer[1024];
		if((nbytes = read (new_fd, buffer, 1024))   ==   -1)
		{
			fprintf(stderr, "Read Error:%s\n", strerror(errno));
			exit (1);            
		}
		buffer[nbytes] = '\0';
		if(buffer[0] == ':') /* delete client from queue */
		{
			int client_pid = strtod(buffer+1, 0);
			if (pthread_mutex_lock(&client_queue_lock)) /* acquire lock client_queue_lock */
				fprintf(stderr, "error in mutex lock\n");
			while(client_queue_process_mod == 1){ /* wait until process is finished */
				pthread_cond_wait(&client_queue_delete, &client_queue_lock);
			}
			client_queue_delete_mod == 1;
			for(i=0; i<MAX_DOMU; i++)
			{
				if(flag_array[i] == 1)
				{
					if(ring_array[i]->domain_pid == client_pid)
					{
						flag_array[i] = 0; /* first set flag as 0, then remove ptr! */
						ring_array[i] = NULL;
						backring_array[i].sring = NULL;
						printf("delete client with pid: %d\n", client_pid);
						break;
					}
				}
			}
			client_queue_delete_mod == 0; /* quit delete mode */
			pthread_mutex_unlock(&client_queue_lock);
			if (pthread_cond_signal(&client_queue_process)) /*signal client_queue_delete allowing delete */
				fprintf(stderr, "error in signaling condition variable\n");
		}
		else
		{
			char * separator = strchr(buffer, ':');
			unsigned int shm_id = strtod(separator+1, 0);
			*(separator) = '\0';
			int client_pid = strtod(buffer, 0);
			printf("Client Pid:%d, shm_id:%d\n", client_pid, shm_id);
			for(i=0; i<MAX_DOMU; i++)
			{
				if(flag_array[i] == 0)
				{
					ring_array[i] = shmat(shm_id, NULL, 0);
					if( ring_array[i] == (void *) -1 || ring_array[i] == NULL ){
						fprintf(stderr, "Fails to attach Shared Memory\n");
						exit (1);
					}
					disk_event_ring_lock_init(ring_array[i]);
					BACK_RING_INIT(&backring_array[i], ring_array[i], SRINGSIZE);
					ring_array[i]->domain_pid = client_pid;
					ring_array[i]->shm_id = shm_id;
					flag_array[i] = 1;
					break;
				}
			}
			if(i == (MAX_DOMU - 1) && ring_array[MAX_DOMU-1] != NULL)
			{
				printf("Cannot serve any more clients\n");
			}
			else
			{ring_array[i]->msg_id = msg_id;
				char temp[128];
				sprintf(temp, "%d:%d:%d:%d", getpid(), msg_id, min_sector, max_sector);
				if(write(new_fd,  temp, strlen(temp)) == -1)
				{
					fprintf(stderr, "Write Error:%s\n", strerror(errno));
      					exit (1);
				}
			}
		}
	}
}

int main(int argc, char **argv)
{
	int i;
	int port = 8080;
	
	ptrs_array = (unsigned long int *)malloc(sizeof(unsigned long int *)*array_size);
	shm_id_array = (int *)malloc(sizeof(int *)*array_size);
		for(i=0; i<array_size; i++)
		shm_id_array[i] = -1;
	/*for (i=1; i<argc; i++)
	{
		char* argi = argv[i];
		if (!strcmp(argi, "-port") || !strcmp(argi, "-p"))
		{
			port = strtod(argv[++i], 0);
		}
		else if (!strcmp(argi, "-background") || !strcmp(argi, "-b"))
		{
			background = strtod(argv[++i], 0);
		}
		else
		{
			fprintf(stderr, "Unrecognized argument\n");
			exit(1);
		}
	}*/
	if(strcmp(argv[1], "start") == 0)
	{
	}
	else if(strcmp(argv[1], "stop") == 0)
	{
		//printf("stop service\n");
		//char *command = "ps aux | grep ./bin/service | grep -v grep | awk '{print $2}'";
		char *command = "pidof ./bin/service";
		char pid_str[16];
		FILE *read_fp;
		read_fp = popen(command, "r");
		if(read_fp != NULL)
		{
			fgets(pid_str, 16, read_fp);
		}
		char kill[32];
		sprintf(kill, "kill -9 %s", pid_str);
		system(kill);
		pclose(read_fp);
		exit(0);
	}
	else
	{
		fprintf(stderr, "Unrecognized argument\n");
		exit(1);
	}
	if(argc >= 3)
	{
		background = strtod(argv[2], 0);
	}
	if(background == 1)
	{
		if(daemon_init() == -1)   
		{   
			printf("can't fork self/n");   
			exit(0);   
		}  
	}

	signal(SIGINT, SIGhandler);
	signal(SIGSEGV, SIGhandler);
	set_timer();

	struct stat statbuf;

	if (stat("./disk1.img", &statbuf) == -1) {
	/* check the value of errno */
		printf("Error getting file size: %s\n", strerror(errno));
	}

	max_sector = (long int) statbuf.st_size/512;
	printf("disk1.img has %d sectors\n", max_sector);

	disk_init();

	char currentdir[256];
        if(getcwd(currentdir, 256)==NULL)
        {
                fprintf(stderr, "Couldn't get current dir\n");
                exit(1);
        }
	key_t msg_key;
	msg_key = ftok(currentdir, -1);
	msg_id = msgget(msg_key, 0644 | IPC_CREAT);//printf("%smsg_id%d\n", currentdir, msg_id);
	msgctl(msg_id, IPC_RMID, 0); /* remove msg queue */

	for(i=0; i<MAX_DOMU; i++)
	{
		//domain_array[i].domain_pid = 0; /* Initial pid value is 0 */
		ring_array[i] = NULL; /* Initial pointer is NULL */
		flag_array[i] = 0;
	}

	if(pthread_mutex_init(&client_queue_lock, NULL)) {
		fprintf(stderr, "Mutex initiation error!\n");
		return 0;
 	}
	if(pthread_cond_init(&client_queue_delete, NULL)) {
		fprintf(stderr, "CV initiation error!\n");
		return 0;
	}
	if(pthread_cond_init(&client_queue_process, NULL)) {
		fprintf(stderr, "CV initiation error!\n");
		return 0;
	}

	//fprintf(stderr, "Starting IO master thread.\n");
        pthread_create(&masterthread, NULL, serve, NULL);

	wait_new_client(port);

	return 0;
}

void  SIGhandler(int sig)
{
	int i;
	char c;
	signal(sig, SIG_IGN);
	if(sig == SIGSEGV){
		fprintf(stderr, "user defined: Segmentataion Fault\n");
	}
	if(sig == SIGINT){
		fprintf(stderr, "Ctrl + c termination\n");
	}
	pthread_cancel(masterthread);
	/*for(i=0; i<MAX_DOMU; i++)
	{
		if( ring_array[i] != (void *) -1 && ring_array[i] != NULL )
		{
			disk_event_ring_destroy(ring_array[i]);
			//shmdt(ring_array[i]);
		}
	}*/
	exit(0);
}
